 /*******************************************************************************
  * Copyright (c) 2000, 2006 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
 package org.eclipse.update.core.model;

 import java.io.File ;
 import java.io.IOException ;
 import java.io.InputStream ;
 import java.util.Calendar ;
 import java.util.GregorianCalendar ;
 import java.util.Iterator ;
 import java.util.Locale ;
 import java.util.Stack ;
 import java.util.StringTokenizer ;

 import javax.xml.parsers.DocumentBuilder ;
 import javax.xml.parsers.DocumentBuilderFactory ;
 import javax.xml.parsers.ParserConfigurationException ;
 import javax.xml.parsers.SAXParser ;
 import javax.xml.parsers.SAXParserFactory ;

 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.MultiStatus;
 import org.eclipse.core.runtime.Platform;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.osgi.util.NLS;
 import org.eclipse.update.core.IURLEntry;
 import org.eclipse.update.core.SiteFeatureReferenceModel;
 import org.eclipse.update.core.URLEntry;
 import org.eclipse.update.internal.core.ExtendedSite;
 import org.eclipse.update.internal.core.Messages;
 import org.eclipse.update.internal.core.UpdateCore;
 import org.w3c.dom.Document ;
 import org.w3c.dom.Element ;
 import org.w3c.dom.NodeList ;
 import org.xml.sax.Attributes ;
 import org.xml.sax.InputSource ;
 import org.xml.sax.SAXException ;
 import org.xml.sax.SAXParseException ;
 import org.xml.sax.helpers.DefaultHandler ;

 /**
  * Default site parser.
  * Parses the site manifest file as defined by the platform. Defers
  * to a model factory to create the actual concrete model objects. The
  * update framework supplies two factory implementations:
  * <ul>
  * <li>@see org.eclipse.update.core.model.SiteModelFactory
  * <li>@see org.eclipse.update.core.BaseSiteFactory
  * </ul>
  *
  * <p>
  * <b>Note:</b> This class/interface is part of an interim API that is still under development and expected to
  * change significantly before reaching stability. It is being made available at this early stage to solicit feedback
  * from pioneering adopters on the understanding that any code that uses this API will almost certainly be broken
  * (repeatedly) as the API evolves.
  * </p>
  * @since 2.0
  */
 public class DefaultSiteParser extends DefaultHandler {
     
     private final static SAXParserFactory parserFactory =
         SAXParserFactory.newInstance();
     
     private SAXParser parser;
     private SiteModelFactory factory;

     private MultiStatus status;

     private boolean DESCRIPTION_SITE_ALREADY_SEEN = false;

     private static final int STATE_IGNORED_ELEMENT = -1;
     private static final int STATE_INITIAL = 0;
     private static final int STATE_SITE = 1;
     private static final int STATE_FEATURE = 2;
     private static final int STATE_ARCHIVE = 3;
     private static final int STATE_CATEGORY = 4;
     private static final int STATE_CATEGORY_DEF = 5;
     private static final int STATE_DESCRIPTION_SITE = 6;
     private static final int STATE_DESCRIPTION_CATEGORY_DEF = 7;
     private static final String PLUGIN_ID = UpdateCore.getPlugin().getBundle().getSymbolicName();

     private static final String SITE = "site"; //$NON-NLS-1$
 private static final String FEATURE = "feature"; //$NON-NLS-1$
 private static final String ARCHIVE = "archive"; //$NON-NLS-1$
 private static final String CATEGORY_DEF = "category-def"; //$NON-NLS-1$
 private static final String CATEGORY = "category"; //$NON-NLS-1$
 private static final String DESCRIPTION = "description"; //$NON-NLS-1$
 private static final String MIRROR = "mirror"; //$NON-NLS-1$
 //private static final String ASSOCIATE_SITES = "associateSites"; //$NON-NLS-1$
 private static final String ASSOCIATE_SITE = "associateSite"; //$NON-NLS-1$

     private static final String DEFAULT_INFO_URL = "index.html"; //$NON-NLS-1$
 private static final String FEATURES = "features/"; //$NON-NLS-1$

     // Current State Information
 Stack stateStack = new Stack ();

     // Current object stack (used to hold the current object we are
 // populating in this plugin descriptor
 Stack objectStack = new Stack ();

     private int currentState;

     /**
      * Constructs a site parser.
      */
     public DefaultSiteParser() {
         super();
         try {
             parserFactory.setNamespaceAware(true);
             this.parser = parserFactory.newSAXParser();
         } catch (ParserConfigurationException e) {
             UpdateCore.log(e);
         } catch (SAXException e) {
             UpdateCore.log(e);
         }

         if (UpdateCore.DEBUG && UpdateCore.DEBUG_SHOW_PARSING)
             debug("Created"); //$NON-NLS-1$
 }

     public void init(SiteModelFactory factory) {
         // PERF: separate instance creation from parsing
 this.factory = factory;
         stateStack = new Stack ();
         objectStack = new Stack ();
         status = null;
         DESCRIPTION_SITE_ALREADY_SEEN = false;
     }

     /**
      * Parses the specified input steam and constructs a site model.
      * The input stream is not closed as part of this operation.
      *
      * @param in input stream
      * @return site model
      * @exception SAXException
      * @exception IOException
      * @since 2.0
      */
     public SiteModel parse(InputStream in) throws SAXException , IOException {
         stateStack.push(new Integer (STATE_INITIAL));
         currentState = ((Integer ) stateStack.peek()).intValue();
         parser.parse(new InputSource (in), this);
         if (objectStack.isEmpty())
             throw new SAXException (Messages.DefaultSiteParser_NoSiteTag);
         else {
             if (objectStack.peek() instanceof SiteModel) {
                 return (SiteModel) objectStack.pop();
             } else {
                 String stack = ""; //$NON-NLS-1$
 Iterator iter = objectStack.iterator();
                 while (iter.hasNext()) {
                     stack = stack + iter.next().toString() + "\r\n"; //$NON-NLS-1$
 }
                 throw new SAXException (NLS.bind(Messages.DefaultSiteParser_WrongParsingStack, (new String [] { stack })));
             }
         }
     }

     /**
      * Returns all status objects accumulated by the parser.
      *
      * @return multi-status containing accumulated status, or <code>null</code>.
      * @since 2.0
      */
     public MultiStatus getStatus() {
         return status;
     }

     /**
      * Handle start of element tags
      * @see DefaultHandler#startElement(String, String, String, Attributes)
      * @since 2.0
      */
     public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {

         if (UpdateCore.DEBUG && UpdateCore.DEBUG_SHOW_PARSING) {
             debug("State: " + currentState); //$NON-NLS-1$
 debug("Start Element: uri:" + uri + " local Name:" + localName + " qName:" + qName);//$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
 }

         switch (currentState) {
             case STATE_IGNORED_ELEMENT :
                 internalErrorUnknownTag(NLS.bind(Messages.DefaultSiteParser_UnknownElement, (new String [] { localName, getState(currentState) })));
                 break;
             case STATE_INITIAL :
                 handleInitialState(localName, attributes);
                 break;

             case STATE_SITE :
                 handleSiteState(localName, attributes);
                 break;

             case STATE_FEATURE :
                 handleFeatureState(localName, attributes);
                 break;

             case STATE_ARCHIVE :
                 handleSiteState(localName, attributes);
                 break;

             case STATE_CATEGORY :
                 handleCategoryState(localName, attributes);
                 break;

             case STATE_CATEGORY_DEF :
                 handleCategoryDefState(localName, attributes);
                 break;

             case STATE_DESCRIPTION_SITE :
                 handleSiteState(localName, attributes);
                 break;

             case STATE_DESCRIPTION_CATEGORY_DEF :
                 handleSiteState(localName, attributes);
                 break;

             default :
                 internalErrorUnknownTag(NLS.bind(Messages.DefaultSiteParser_UnknownStartState, (new String [] { getState(currentState) })));
                 break;
         }
         int newState = ((Integer ) stateStack.peek()).intValue();
         if (newState != STATE_IGNORED_ELEMENT)
             currentState = newState;

     }

     /**
      * Handle end of element tags
      * @see DefaultHandler#endElement(String, String, String)
      * @since 2.0
      */
     public void endElement(String uri, String localName, String qName) {

         String text = null;
         URLEntryModel info = null;

         int state = ((Integer ) stateStack.peek()).intValue();
         switch (state) {
             case STATE_IGNORED_ELEMENT :
             case STATE_ARCHIVE :
             case STATE_CATEGORY :
                 stateStack.pop();
                 break;

             case STATE_INITIAL :
                 internalError(Messages.DefaultSiteParser_ParsingStackBackToInitialState);
                 break;

             case STATE_SITE :
                 stateStack.pop();
                 if (objectStack.peek() instanceof String ) {
                     text = (String ) objectStack.pop();
                     SiteModel site = (SiteModel) objectStack.peek();
                     site.getDescriptionModel().setAnnotation(text);
                 }
                 //do not pop the object
 break;

             case STATE_FEATURE :
                 stateStack.pop();
                 objectStack.pop();
                 break;

             case STATE_CATEGORY_DEF :
                 stateStack.pop();
                 if (objectStack.peek() instanceof String ) {
                     text = (String ) objectStack.pop();
                     CategoryModel category = (CategoryModel) objectStack.peek();
                     category.getDescriptionModel().setAnnotation(text);
                 }
                 objectStack.pop();
                 break;

             case STATE_DESCRIPTION_SITE :
                 stateStack.pop();
                 text = ""; //$NON-NLS-1$
 while (objectStack.peek() instanceof String ) {
                     // add text, preserving at most one space between text fragments
 String newText = (String ) objectStack.pop();
                     if (trailingSpace(newText) && !leadingSpace(text)) {
                         text = " " + text; //$NON-NLS-1$
 }
                     text = newText.trim() + text;
                     if (leadingSpace(newText) && !leadingSpace(text)) {
                         text = " " + text; //$NON-NLS-1$
 }
                 }
                 text = text.trim();

                 info = (URLEntryModel) objectStack.pop();
                 if (text != null)
                     info.setAnnotation(text);

                 SiteModel siteModel = (SiteModel) objectStack.peek();
                 // override description.
 // do not raise error as previous description may be default one
 // when parsing site tag
 if (DESCRIPTION_SITE_ALREADY_SEEN)
                     debug(NLS.bind(Messages.DefaultSiteParser_ElementAlreadySet, (new String [] { getState(state) })));
                 siteModel.setDescriptionModel(info);
                 DESCRIPTION_SITE_ALREADY_SEEN = true;
                 break;

             case STATE_DESCRIPTION_CATEGORY_DEF :
                 stateStack.pop();
                 text = ""; //$NON-NLS-1$
 while (objectStack.peek() instanceof String ) {
                     // add text, preserving at most one space between text fragments
 String newText = (String ) objectStack.pop();
                     if (trailingSpace(newText) && !leadingSpace(text)) {
                         text = " " + text; //$NON-NLS-1$
 }
                     text = newText.trim() + text;
                     if (leadingSpace(newText) && !leadingSpace(text)) {
                         text = " " + text; //$NON-NLS-1$
 }
                 }
                 text = text.trim();

                 info = (URLEntryModel) objectStack.pop();
                 if (text != null)
                     info.setAnnotation(text);

                 CategoryModel category = (CategoryModel) objectStack.peek();
                 if (category.getDescriptionModel() != null)
                     internalError(NLS.bind(Messages.DefaultSiteParser_ElementAlreadySet, (new String [] { getState(state), category.getLabel() })));
                 else
                     category.setDescriptionModel(info);
                 break;

             default :
                 internalError(NLS.bind(Messages.DefaultSiteParser_UnknownEndState, (new String [] { getState(state) })));
                 break;
         }

         if (UpdateCore.DEBUG && UpdateCore.DEBUG_SHOW_PARSING)
             debug("End Element:" + uri + ":" + localName + ":" + qName);//$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
 }

     /**
      * Handle character text
      * @see DefaultHandler#characters(char[], int, int)
      * @since 2.0
      */
     public void characters(char[] ch, int start, int length) {
         String text = new String (ch, start, length);
         //only push if description
 int state = ((Integer ) stateStack.peek()).intValue();
         if (state == STATE_DESCRIPTION_SITE || state == STATE_DESCRIPTION_CATEGORY_DEF)
             objectStack.push(text);

     }

     /**
      * Handle errors
      * @see DefaultHandler#error(SAXParseException)
      * @since 2.0
      */
     public void error(SAXParseException ex) {
         logStatus(ex);
     }

     /**
      * Handle fatal errors
      * @see DefaultHandler#fatalError(SAXParseException)
      * @exception SAXException
      * @since 2.0
      */
     public void fatalError(SAXParseException ex) throws SAXException {
         logStatus(ex);
         throw ex;
     }

     private void handleInitialState(String elementName, Attributes attributes) throws SAXException {
         if (elementName.equals(SITE)) {
             stateStack.push(new Integer (STATE_SITE));
             processSite(attributes);
         } else {
             internalErrorUnknownTag(NLS.bind(Messages.DefaultSiteParser_UnknownElement, (new String [] { elementName, getState(currentState) })));
             // what we received was not a site.xml, no need to continue
 throw new SAXException (Messages.DefaultSiteParser_InvalidXMLStream);
         }

     }

     private void handleSiteState(String elementName, Attributes attributes) {
         if (elementName.equals(DESCRIPTION)) {
             stateStack.push(new Integer (STATE_DESCRIPTION_SITE));
             processInfo(attributes);
         } else if (elementName.equals(FEATURE)) {
             stateStack.push(new Integer (STATE_FEATURE));
             processFeature(attributes);
         } else if (elementName.equals(ARCHIVE)) {
             stateStack.push(new Integer (STATE_ARCHIVE));
             processArchive(attributes);
         } else if (elementName.equals(CATEGORY_DEF)) {
             stateStack.push(new Integer (STATE_CATEGORY_DEF));
             processCategoryDef(attributes);
         } else
             internalErrorUnknownTag(NLS.bind(Messages.DefaultSiteParser_UnknownElement, (new String [] { elementName, getState(currentState) })));
     }

     private void handleFeatureState(String elementName, Attributes attributes) {
         if (elementName.equals(DESCRIPTION)) {
             stateStack.push(new Integer (STATE_DESCRIPTION_SITE));
             processInfo(attributes);
         } else if (elementName.equals(FEATURE)) {
             stateStack.push(new Integer (STATE_FEATURE));
             processFeature(attributes);
         } else if (elementName.equals(ARCHIVE)) {
             stateStack.push(new Integer (STATE_ARCHIVE));
             processArchive(attributes);
         } else if (elementName.equals(CATEGORY_DEF)) {
             stateStack.push(new Integer (STATE_CATEGORY_DEF));
             processCategoryDef(attributes);
         } else if (elementName.equals(CATEGORY)) {
             stateStack.push(new Integer (STATE_CATEGORY));
             processCategory(attributes);
         } else
             internalErrorUnknownTag(NLS.bind(Messages.DefaultSiteParser_UnknownElement, (new String [] { elementName, getState(currentState) })));
     }

     private void handleCategoryDefState(String elementName, Attributes attributes) {
         if (elementName.equals(FEATURE)) {
             stateStack.push(new Integer (STATE_FEATURE));
             processFeature(attributes);
         } else if (elementName.equals(ARCHIVE)) {
             stateStack.push(new Integer (STATE_ARCHIVE));
             processArchive(attributes);
         } else if (elementName.equals(CATEGORY_DEF)) {
             stateStack.push(new Integer (STATE_CATEGORY_DEF));
             processCategoryDef(attributes);
         } else if (elementName.equals(DESCRIPTION)) {
             stateStack.push(new Integer (STATE_DESCRIPTION_CATEGORY_DEF));
             processInfo(attributes);
         } else
             internalErrorUnknownTag(NLS.bind(Messages.DefaultSiteParser_UnknownElement, (new String [] { elementName, getState(currentState) })));
     }

     private void handleCategoryState(String elementName, Attributes attributes) {
         if (elementName.equals(DESCRIPTION)) {
             stateStack.push(new Integer (STATE_DESCRIPTION_SITE));
             processInfo(attributes);
         } else if (elementName.equals(FEATURE)) {
             stateStack.push(new Integer (STATE_FEATURE));
             processFeature(attributes);
         } else if (elementName.equals(ARCHIVE)) {
             stateStack.push(new Integer (STATE_ARCHIVE));
             processArchive(attributes);
         } else if (elementName.equals(CATEGORY_DEF)) {
             stateStack.push(new Integer (STATE_CATEGORY_DEF));
             processCategoryDef(attributes);
         } else if (elementName.equals(CATEGORY)) {
             stateStack.push(new Integer (STATE_CATEGORY));
             processCategory(attributes);
         } else
             internalErrorUnknownTag(NLS.bind(Messages.DefaultSiteParser_UnknownElement, (new String [] { elementName, getState(currentState) })));
     }

     /*
      * process site info
      */
     private void processSite(Attributes attributes) throws SAXException {
         // create site map
 SiteModel site = factory.createSiteMapModel();

         // if URL is specified, it replaces the URL of the site
 // used to calculate the location of features and archives
 String siteURL = attributes.getValue("url"); //$NON-NLS-1$
 if (siteURL != null && !("".equals(siteURL.trim()))) { //$NON-NLS-1$
 if (!siteURL.endsWith("/") && !siteURL.endsWith(File.separator)) { //$NON-NLS-1$
 siteURL += "/"; //$NON-NLS-1$
 }
             site.setLocationURLString(siteURL);
         }

         // provide default description URL
 // If <description> is specified, for the site, it takes precedence
 URLEntryModel description = factory.createURLEntryModel();
         description.setURLString(DEFAULT_INFO_URL);
         site.setDescriptionModel(description);

         // verify we can parse the site ...if the site has
 // a different type throw an exception to force reparsing
 // with the matching parser
 String type = attributes.getValue("type"); //$NON-NLS-1$
 if (!factory.canParseSiteType(type)) {
             throw new SAXException (new InvalidSiteTypeException(type));
         }
         site.setType(type);
         
         // get mirrors, if any
 String mirrorsURL = attributes.getValue("mirrorsURL"); //$NON-NLS-1$
 if (mirrorsURL != null && mirrorsURL.trim().length() > 0) {
             URLEntryModel[] mirrors = getMirrors(mirrorsURL, factory);
             if (mirrors != null)
                 site.setMirrorSiteEntryModels(mirrors);
             else
                 site.setMirrorsURLString(mirrorsURL);
         }
         
         String pack200 = attributes.getValue("pack200"); //$NON-NLS-1$
 if(site instanceof ExtendedSite && pack200 != null && new Boolean (pack200).booleanValue()){
             ((ExtendedSite) site).setSupportsPack200(true);
         }
         
         if ( (site instanceof ExtendedSite) && (attributes.getValue("digestURL") != null)) { //$NON-NLS-1$
 ExtendedSite extendedSite = (ExtendedSite) site;
             extendedSite.setDigestExist(true);
             extendedSite.setDigestURL(attributes.getValue("digestURL")); //$NON-NLS-1$

             if ( (attributes.getValue("availableLocales") != null) && (!attributes.getValue("availableLocales").trim().equals(""))) { //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$
 StringTokenizer locals = new StringTokenizer (attributes.getValue("availableLocales"), ","); //$NON-NLS-1$//$NON-NLS-2$
 String [] availableLocals = new String [locals.countTokens()];
                 int i = 0;
                 while(locals.hasMoreTokens()) {
                     availableLocals[i++] = locals.nextToken();
                 }
                 extendedSite.setAvailableLocals(availableLocals);
             }
         }
         
         if ( (site instanceof ExtendedSite) && (attributes.getValue("associateSitesURL") != null)) { //$NON-NLS-1$
 IURLEntry[] associateSites = getAssociateSites(attributes.getValue("associateSitesURL"), factory); //$NON-NLS-1$
 if (associateSites != null)
                 ((ExtendedSite)site).setAssociateSites(associateSites);
             else
                 site.setMirrorsURLString(mirrorsURL);
         }
         
         objectStack.push(site);

         if (UpdateCore.DEBUG && UpdateCore.DEBUG_SHOW_PARSING)
             debug("End process Site tag: siteURL:" + siteURL + " type:" + type);//$NON-NLS-1$ //$NON-NLS-2$

     }

     /*
      * process feature info
      */
     private void processFeature(Attributes attributes) {
         SiteFeatureReferenceModel feature = factory.createFeatureReferenceModel();
         
         // feature location on the site
 String urlInfo = attributes.getValue("url"); //$NON-NLS-1$
 // identifier and version
 String id = attributes.getValue("id"); //$NON-NLS-1$
 String ver = attributes.getValue("version"); //$NON-NLS-1$

         boolean noURL = (urlInfo == null || urlInfo.trim().equals("")); //$NON-NLS-1$
 boolean noId = (id == null || id.trim().equals("")); //$NON-NLS-1$
 boolean noVersion = (ver == null || ver.trim().equals("")); //$NON-NLS-1$

         // We need to have id and version, or the url, or both.
 if (noURL) {
             if (noId || noVersion)
                 internalError(NLS.bind(Messages.DefaultSiteParser_Missing, (new String [] { "url", getState(currentState) }))); //$NON-NLS-1$
 else // default url
 urlInfo = FEATURES + id + '_' + ver; //
 }
         
         feature.setURLString(urlInfo);

         String type = attributes.getValue("type"); //$NON-NLS-1$
 feature.setType(type);

         // if one is null, and not the other
 if (noId ^ noVersion) {
             String [] values = new String [] { id, ver, getState(currentState)};
             UpdateCore.warn(NLS.bind(Messages.DefaultFeatureParser_IdOrVersionInvalid, values));
         } else {
             feature.setFeatureIdentifier(id);
             feature.setFeatureVersion(ver);
         }

         // get label if it exists
 String label = attributes.getValue("label"); //$NON-NLS-1$
 if (label != null) {
             if ("".equals(label.trim())) //$NON-NLS-1$
 label = null;
         }
         feature.setLabel(label);

         // OS
 String os = attributes.getValue("os"); //$NON-NLS-1$
 feature.setOS(os);

         // WS
 String ws = attributes.getValue("ws"); //$NON-NLS-1$
 feature.setWS(ws);

         // NL
 String nl = attributes.getValue("nl"); //$NON-NLS-1$
 feature.setNL(nl);

         // arch
 String arch = attributes.getValue("arch"); //$NON-NLS-1$
 feature.setArch(arch);

         //patch
 String patch = attributes.getValue("patch"); //$NON-NLS-1$
 feature.setPatch(patch);

         SiteModel site = (SiteModel) objectStack.peek();
         site.addFeatureReferenceModel(feature);
         feature.setSiteModel(site);

         objectStack.push(feature);

         if (UpdateCore.DEBUG && UpdateCore.DEBUG_SHOW_PARSING)
             debug("End Processing DefaultFeature Tag: url:" + urlInfo + " type:" + type); //$NON-NLS-1$ //$NON-NLS-2$

     }

     /*
      * process archive info
      */
     private void processArchive(Attributes attributes) {
         ArchiveReferenceModel archive = factory.createArchiveReferenceModel();
         String id = attributes.getValue("path"); //$NON-NLS-1$
 if (id == null || id.trim().equals("")) { //$NON-NLS-1$
 internalError(NLS.bind(Messages.DefaultSiteParser_Missing, (new String [] { "path", getState(currentState) }))); //$NON-NLS-1$
 }

         archive.setPath(id);

         String url = attributes.getValue("url"); //$NON-NLS-1$
 if (url == null || url.trim().equals("")) { //$NON-NLS-1$
 internalError(NLS.bind(Messages.DefaultSiteParser_Missing, (new String [] { "archive", getState(currentState) }))); //$NON-NLS-1$
 } else {
             archive.setURLString(url);

             SiteModel site = (SiteModel) objectStack.peek();
             site.addArchiveReferenceModel(archive);
         }
         if (UpdateCore.DEBUG && UpdateCore.DEBUG_SHOW_PARSING)
             debug("End processing Archive: path:" + id + " url:" + url);//$NON-NLS-1$ //$NON-NLS-2$

     }

     /*
      * process the Category info
      */
     private void processCategory(Attributes attributes) {
         String category = attributes.getValue("name"); //$NON-NLS-1$
 SiteFeatureReferenceModel feature = (SiteFeatureReferenceModel) objectStack.peek();
         feature.addCategoryName(category);

         if (UpdateCore.DEBUG && UpdateCore.DEBUG_SHOW_PARSING)
             debug("End processing Category: name:" + category); //$NON-NLS-1$
 }

     /*
      * process category def info
      */
     private void processCategoryDef(Attributes attributes) {
         CategoryModel category = factory.createSiteCategoryModel();
         String name = attributes.getValue("name"); //$NON-NLS-1$
 String label = attributes.getValue("label"); //$NON-NLS-1$
 category.setName(name);
         category.setLabel(label);

         SiteModel site = (SiteModel) objectStack.peek();
         site.addCategoryModel(category);
         objectStack.push(category);

         if (UpdateCore.DEBUG && UpdateCore.DEBUG_SHOW_PARSING)
             debug("End processing CategoryDef: name:" + name + " label:" + label); //$NON-NLS-1$ //$NON-NLS-2$
 }

     /*
      * process URL info with element text
      */
     private void processInfo(Attributes attributes) {
         URLEntryModel inf = factory.createURLEntryModel();
         String infoURL = attributes.getValue("url"); //$NON-NLS-1$
 inf.setURLString(infoURL);

         if (UpdateCore.DEBUG && UpdateCore.DEBUG_SHOW_PARSING)
             debug("Processed Info: url:" + infoURL); //$NON-NLS-1$

         objectStack.push(inf);
     }

     /*
      *
      */
     private static void debug(String s) {
         UpdateCore.debug("DefaultSiteParser" + s); //$NON-NLS-1$
 }

     /*
      *
      */
     private void logStatus(SAXParseException ex) {
         String name = ex.getSystemId();
         if (name == null)
             name = ""; //$NON-NLS-1$
 else
             name = name.substring(1 + name.lastIndexOf("/")); //$NON-NLS-1$

         String msg;
         if (name.equals("")) //$NON-NLS-1$
 msg = NLS.bind(Messages.DefaultSiteParser_ErrorParsing, (new String [] { ex.getMessage() }));
         else {
             String [] values = new String [] { name, Integer.toString(ex.getLineNumber()), Integer.toString(ex.getColumnNumber()), ex.getMessage()};
             msg = NLS.bind(Messages.DefaultSiteParser_ErrorlineColumnMessage, values);
         }
         error(new Status(IStatus.ERROR, PLUGIN_ID, Platform.PARSE_PROBLEM, msg, ex));
     }

     /*
      * Handles an error state specified by the status. The collection of all logged status
      * objects can be accessed using <code>getStatus()</code>.
      *
      * @param error a status detailing the error condition
      */
     private void error(IStatus error) {

         if (status == null) {
             status = new MultiStatus(PLUGIN_ID, Platform.PARSE_PROBLEM, Messages.DefaultSiteParser_ErrorParsingSite, null);
         }

         status.add(error);
         if (UpdateCore.DEBUG && UpdateCore.DEBUG_SHOW_PARSING)
             UpdateCore.log(error);
     }

     /*
      *
      */
     private void internalErrorUnknownTag(String msg) {
         stateStack.push(new Integer (STATE_IGNORED_ELEMENT));
         internalError(msg);
     }

     /*
      *
      */
     private void internalError(String message) {
         error(new Status(IStatus.ERROR, PLUGIN_ID, IStatus.OK, message, null));
     }

     /*
      * return the state as String
      */
     private String getState(int state) {

         switch (state) {
             case STATE_IGNORED_ELEMENT :
                 return "Ignored"; //$NON-NLS-1$

             case STATE_INITIAL :
                 return "Initial"; //$NON-NLS-1$

             case STATE_SITE :
                 return "Site"; //$NON-NLS-1$

             case STATE_FEATURE :
                 return "Feature"; //$NON-NLS-1$

             case STATE_ARCHIVE :
                 return "Archive"; //$NON-NLS-1$

             case STATE_CATEGORY :
                 return "Category"; //$NON-NLS-1$

             case STATE_CATEGORY_DEF :
                 return "Category Def"; //$NON-NLS-1$

             case STATE_DESCRIPTION_CATEGORY_DEF :
                 return "Description / Category Def"; //$NON-NLS-1$

             case STATE_DESCRIPTION_SITE :
                 return "Description / Site"; //$NON-NLS-1$

             default :
                 return Messages.DefaultSiteParser_UnknownState;
         }
     }
     private boolean leadingSpace(String str) {
         if (str.length() <= 0) {
             return false;
         }
         return Character.isWhitespace(str.charAt(0));
     }
     private boolean trailingSpace(String str) {
         if (str.length() <= 0) {
             return false;
         }
         return Character.isWhitespace(str.charAt(str.length() - 1));
     }
     
     static URLEntryModel[] getMirrors(String mirrorsURL, SiteModelFactory factory) {
         
         try {
             String countryCode = Locale.getDefault().getCountry().toLowerCase();
             int timeZone = (new GregorianCalendar ()).get(Calendar.ZONE_OFFSET)/(60*60*1000);

             if (mirrorsURL.indexOf("?") != -1) { //$NON-NLS-1$
 mirrorsURL = mirrorsURL + "&"; //$NON-NLS-1$
 } else {
                 mirrorsURL = mirrorsURL + "?"; //$NON-NLS-1$
 }
             mirrorsURL = mirrorsURL + "countryCode=" + countryCode + "&timeZone=" + timeZone + "&responseType=xml"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$

             DocumentBuilderFactory domFactory =
             DocumentBuilderFactory.newInstance();
             DocumentBuilder builder = domFactory.newDocumentBuilder();
             Document document = builder.parse(mirrorsURL);
             if (document == null)
                 return null;
             NodeList mirrorNodes = document.getElementsByTagName(MIRROR);
             URLEntryModel[] mirrors = new URLEntryModel[mirrorNodes.getLength()];
             for (int i=0; i<mirrorNodes.getLength(); i++) {
                 Element mirrorNode = (Element )mirrorNodes.item(i);
                 mirrors[i] = factory.createURLEntryModel();
                 String infoURL = mirrorNode.getAttribute("url"); //$NON-NLS-1$
 String label = mirrorNode.getAttribute("label"); //$NON-NLS-1$
 mirrors[i].setURLString(infoURL);
                 mirrors[i].setAnnotation(label);

                 if (UpdateCore.DEBUG && UpdateCore.DEBUG_SHOW_PARSING)
                     debug("Processed mirror: url:" + infoURL + " label:" + label); //$NON-NLS-1$ //$NON-NLS-2$
 }
             return mirrors;
         }
         catch (Exception e) {
             // log if absolute url
 if (mirrorsURL != null &&
                     (mirrorsURL.startsWith("http://") //$NON-NLS-1$
 || mirrorsURL.startsWith("https://") //$NON-NLS-1$
 || mirrorsURL.startsWith("file://") //$NON-NLS-1$
 || mirrorsURL.startsWith("ftp://") //$NON-NLS-1$
 || mirrorsURL.startsWith("jar://"))) //$NON-NLS-1$
 UpdateCore.log(Messages.DefaultSiteParser_mirrors, e);
             return null;
         }
     }
     
     private static IURLEntry[] getAssociateSites(String associateSitesURL, SiteModelFactory factory) {
         
         try {
             DocumentBuilderFactory domFactory =
             DocumentBuilderFactory.newInstance();
             DocumentBuilder builder = domFactory.newDocumentBuilder();
             Document document = builder.parse(associateSitesURL);
             if (document == null)
                 return null;
             NodeList mirrorNodes = document.getElementsByTagName(ASSOCIATE_SITE);
             URLEntry[] mirrors = new URLEntry[mirrorNodes.getLength()];
             for (int i=0; i<mirrorNodes.getLength(); i++) {
                 Element mirrorNode = (Element )mirrorNodes.item(i);
                 mirrors[i] = new URLEntry();
                 String infoURL = mirrorNode.getAttribute("url"); //$NON-NLS-1$
 String label = mirrorNode.getAttribute("label"); //$NON-NLS-1$
 mirrors[i].setURLString(infoURL);
                 mirrors[i].setAnnotation(label);

                 if (UpdateCore.DEBUG && UpdateCore.DEBUG_SHOW_PARSING)
                     debug("Processed mirror: url:" + infoURL + " label:" + label); //$NON-NLS-1$ //$NON-NLS-2$
 }
             return mirrors;
         }
         catch (Exception e) {
             // log if absolute url
 if (associateSitesURL != null &&
                     (associateSitesURL.startsWith("http://") //$NON-NLS-1$
 || associateSitesURL.startsWith("https://") //$NON-NLS-1$
 || associateSitesURL.startsWith("file://") //$NON-NLS-1$
 || associateSitesURL.startsWith("ftp://") //$NON-NLS-1$
 || associateSitesURL.startsWith("jar://"))) //$NON-NLS-1$
 UpdateCore.log(Messages.DefaultSiteParser_mirrors, e);
             return null;
         }
     }
     
 }

